
// Copyright (c) WanSheng Intelligent Corp. All rights reserved.
// Licensed under the LGPL v3.0 license. 


#include "modbus_plugin.h"

bool check_link_reconnection()
{
    // when the USB is disconnected, the returned errno is EIO (5)
    if ((errno == EBADF || errno == ECONNRESET || errno == EPIPE || EIO == errno))
        return true;

    return false;
}


// for coil operation, one register need one byte buffer.
// for other, one register needs two bytes of buffer
int exec_modbus_command(i_modbus_t * bus, int fc, int addr, int nb, char * in, char * out)
{
    int rc = 0;
    modbus_t * bus_ctx = bus->bus_ctx;
    tick_time_t now = bh_get_tick_ms();
    if(bus->min_wait_ms > 0 && (now-bus->last_access) < bus->min_wait_ms)
    {
        usleep((bus->min_wait_ms + bus->last_access - now) * 1000);
    }

    if(bus->response_wait_ms > 10 && bus->response_wait_ms < 10*1000)
    {
        modbus_set_response_timeout(bus->bus_ctx,
                bus->response_wait_ms/1000,
                (bus->response_wait_ms%1000)*1000);
    }

    modbus_set_slave(bus->bus_ctx, bus->current_slave_id);

    int bus_last_access = bh_get_tick_ms() - bus->last_access;
    bus->recent_errno = 0;

    switch(fc)
    {
    case MODBUS_FC_READ_COILS:
        rc = modbus_read_bits(bus_ctx, addr, nb, (uint8_t*)out);
        break;

    case MODBUS_FC_READ_DISCRETE_INPUTS:
        rc = modbus_read_input_bits(bus_ctx, addr, nb, (uint8_t*)out);
        break;

    case MODBUS_FC_READ_HOLDING_REGISTERS:
        rc = modbus_read_registers(bus_ctx, addr, nb, (uint16_t *)out);
        break;

    case MODBUS_FC_READ_INPUT_REGISTERS:
        rc = modbus_read_input_registers(bus_ctx, addr, nb, (uint16_t *)out);
        break;

    case MODBUS_FC_WRITE_SINGLE_COIL:
        rc = modbus_write_bit(bus_ctx, addr, *in);
        break;

    case MODBUS_FC_WRITE_SINGLE_REGISTER:
        rc = modbus_write_register(bus_ctx, addr, ((int)*((uint16_t *)in)));

        break;
    case MODBUS_FC_READ_EXCEPTION_STATUS:
        break;

    case MODBUS_FC_WRITE_MULTIPLE_COILS:
        rc = modbus_write_bits(bus_ctx, addr, nb, (const uint8_t*)in);
        break;

    case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
        rc = modbus_write_registers(bus_ctx, addr, nb, (uint16_t *)in);
        break;

    case MODBUS_FC_REPORT_SLAVE_ID:
        break;

    case MODBUS_FC_MASK_WRITE_REGISTER:
        break;

    case MODBUS_FC_WRITE_AND_READ_REGISTERS:
        //rc = modbus_write_and_read_registers(bus->bus_ctx, cmd->addr, nb, cmd->data);
        break;
    default:
        break;
    }

    if(rc == -1)
    {
        bus->recent_errno = errno;

        // avoid too many print
        static tick_time_t last = 0;
        tick_time_t now = bh_get_tick_ms()  ;

        bus->fails ++;
        bus->total_fails ++;

        if(now - last > 60 * 1000 || LOGI_SET(FLAG_MODBUS_ACCESS))
        {
            WALOG("Bus [%s] error (%s). slave=%d, fc=%d, addr=%d, num=%d. "  \
                    "\n\t%d ms since last access, cont'd fails: %u, total fails: %u",
                    SAFE_STRING(bus_name(bus)), modbus_strerror(errno),
                    bus->current_slave_id,
                    fc, addr , nb,
                    bus_last_access,
                    bus->fails,
                    bus->total_fails);

            last = now ;
        }

        // for RTU, sometime the USB adapter can be removed, so it should be checked too.
        if(check_link_reconnection())
        {
            disconnet_modbus(bus);
        }
    }
    else
    {
        bus->fails = 0;
        TraceV(FLAG_MODBUS_SUCCESS, "access OK. [%s] slave=%d, fc=%d, addr=%d, num=%d",
                SAFE_STRING(bus_name(bus)),
                bus->current_slave_id,
                fc, addr , nb);
    }

    bus->last_access = bh_get_tick_ms();

    return rc;
}



